/*
 * World.cpp
 *
 *  Created on: 27-12-2010
 *      Author: Przemek
 */

#include "world/World.hpp"
#include "utils/VectorUtils.hpp"
#include "world/Barrier.hpp"
#include "world/MeatSupply.hpp"
#include "world/VegetableSupply.hpp"
#include "world/Individual.hpp"
#include "utils/VectorUtils.hpp"
#include "utils/Randomizer.hpp"
#include "world/ElementsVisitor.hpp"
#include "pch.hpp"
using namespace boost;

const geometry::Rectangle World::BOUNDS(Vector(0, 0), Dimension(600, 600));

World& World::getInstance()
{
	static World world;
	return world;
}

void World::updateWithDeltaTime(int timeSpeedMultiplication, float deltaTimeInSeconds)
{
	if(running)
	{
		for(int i = 0; i < timeSpeedMultiplication; ++i)
		{
			scheduledToAdd.clear();
			updateElements(deltaTimeInSeconds);
			growPlantsRandomly();
			spreadWater();
		}
	}
}

const World::ElementsVector& World::getElements() const
{
	return elements;
}

const World::ElementsVector World::getElements(const CircleSlice& circleSlice) const
{
	ElementsVector vector;
	BOOST_FOREACH(const boost::shared_ptr<Element>& element, elements)
				{
					if(circleSlice.containsPartially(element.get()->getBounds()))
					{
						if(circleSlice.getPosition() != element.get()->getBounds().getCenter())
						{
							vector.push_back(element);
						}
					}
				}

	return vector;
}

World::ElementsVector World::getNeighborhoodBarriers(const Vector& position)
{
	ElementsVector vector;
	BOOST_FOREACH(const boost::shared_ptr<Element>& element, elements)
				{
					if((element.get()->getBounds().getCenter() - position).getLength() < 2 * Individual::SIZE)
					{
						vector.push_back(element);
					}
				}
	return vector;
}

bool World::isRunning() const
{
	return running;
}

void World::setRunning(bool running)
{
	this->running = running;
}

void World::add(Element* element)
{
	boost::shared_ptr<Element> toAdd = boost::shared_ptr<Element>(element);
	add(toAdd);
}

void World::add(boost::shared_ptr<Element>& element)
{
	if(VectorUtils::contains<boost::shared_ptr<Element> >(elements, element) == false)
	{
		elements.push_back(element);
	}
}

void World::remove(Element* element)
{
	ElementsVector::iterator iter = VectorUtils::findInSharedPtrVector<Element>(elements, element);
	if(iter != elements.end())
	{
		elements.erase(iter);
	}
}

void World::scheduleToAdd(Element* element)
{
	boost::shared_ptr<Element> toAdd = boost::shared_ptr<Element>(element);
	if(VectorUtils::contains<boost::shared_ptr<Element> >(scheduledToAdd, toAdd) == false)
	{
		scheduledToAdd.push_back(toAdd);
	}
}

Element* World::getElementAt(const Vector& point)
{
	BOOST_FOREACH(const boost::shared_ptr<Element>& element, elements)
				{
					if(element->getBounds().contains(point))
					{
						return element.get();
					}
				}
	return 0;
}

bool World::canPlaceAt(const geometry::Rectangle& rectangle) const
{
	if(BOUNDS.intersects(rectangle) == false)
	{
		return false;
	}
	BOOST_FOREACH(const boost::shared_ptr<Element>& element, elements)
				{
					if(element->getBounds().intersects(rectangle))
					{
						return false;
					}
				}
	return true;
}

World::World() :
	running(false)
{
	srand(static_cast<unsigned int> (time(NULL)));

	const float worldWidth = BOUNDS.getSize().getWidth();
	const float worldHeight = BOUNDS.getSize().getHeight();

	static const float globalBarrierSize = Barrier::SIZE;

	//LEFT WORLD BARRIER
	for(int i = worldHeight / globalBarrierSize; i >= 0; --i)
	{
		this->add(new Barrier(Vector(0.0f, i * globalBarrierSize)));
	}

	//TOP WORLD BARRIER
	for(int i = (worldWidth - 2 * globalBarrierSize) / globalBarrierSize; i >= 0; --i)
	{
		this->add(new Barrier(Vector(globalBarrierSize * (i + 1), 0.0f)));
	}

	//RIGHT WORLD BARRIER
	for(int i = worldHeight / globalBarrierSize; i >= 0; --i)
	{
		this->add(new Barrier(Vector(worldWidth - globalBarrierSize, i * globalBarrierSize)));
	}

	//BOTTOM WORLD BARRIER
	for(int i = (worldWidth - 2 * globalBarrierSize) / globalBarrierSize; i >= 0; --i)
	{
		this->add(new Barrier(Vector(globalBarrierSize * (i + 1), worldHeight - globalBarrierSize)));
	}
}

void World::updateElements(float deltaTimeInSeconds)
{
	ElementsVector toDeletes;
	BOOST_FOREACH (boost::shared_ptr<Element>& element, elements)
				{
					element->updateWithDeltaTime(deltaTimeInSeconds);
					if(element->isToDelete())
					{
						toDeletes.push_back(element);
					}
				}

	BOOST_FOREACH(boost::shared_ptr<Element>& toDelete, toDeletes)
				{
					ElementsVector::iterator it = VectorUtils::find<boost::shared_ptr<Element> >(elements,
							toDelete);
					if(it != elements.end()) // should always be evaluated to true
					{
						if(dynamic_cast<Individual*> (it->get()) != 0)
						{
							scheduleToAdd(new MeatSupply(it->get()->getPosition()));
						}
						elements.erase(it);
					}
				}

	BOOST_FOREACH(boost::shared_ptr<Element>& toAdd, scheduledToAdd)
				{
					elements.push_back(toAdd);
				}
}

void World::growPlantsRandomly()
{
	if(Randomizer::nextUniformFloat() < VegetableSupply::CHANCE_TO_GROW)
	{
		const int worldWidth = static_cast<int> (World::BOUNDS.getSize().getWidth());
		const int worldHeight = static_cast<int> (World::BOUNDS.getSize().getHeight());

		const Vector& position = Vector(Randomizer::nextInt(worldWidth), Randomizer::nextInt(
				worldHeight));
		const Dimension& size = Dimension(VegetableSupply::SIZE, VegetableSupply::SIZE);
		const geometry::Rectangle& bounds = geometry::Rectangle(position, size);
		if(canPlaceAt(bounds))
		{
			add(new VegetableSupply(position));
		}
	}
}

void World::spreadWater()
{
	if(Randomizer::nextUniformFloat() < WaterSupply::CHANCE_OF_RAIN)
	{
		const int worldWidth = static_cast<int> (World::BOUNDS.getSize().getWidth());
		const int worldHeight = static_cast<int> (World::BOUNDS.getSize().getHeight());

		const Vector& position = Vector(Randomizer::nextInt(worldWidth), Randomizer::nextInt(
				worldHeight));
		const Dimension& size = Dimension(WaterSupply::SIZE, WaterSupply::SIZE);
		const geometry::Rectangle& bounds = geometry::Rectangle(position, size);
		if(canPlaceAt(bounds))
		{
			add(new WaterSupply(position));
		}
	}
}
